Contentful(たぶん待望の)新機能 “ライブプレビュー” がリリース! リアルタイムな記事確認&編集が可能になりました。
ベルリンオフィスの小西です!。
ヘッドレス CMS の Contentful からライブプレビュー機能がリリースされました! 記事執筆体験を向上させてくれる大きなアップグレードで、本記事では導入方法の紹介とともに、公開されているリポジトリ内容も自分なりに解説してみます。
これまで
フロントエンドを持たない(= ヘッドレスな)Contentfulでは、編集した記事が最終的にどんな見た目になるのか確認するために、フロントアプリケーションまで移動し、そちらの更新やビルドを待つ必要がありました。
↓
これから
Contentful上の記事エディターと同じページ、かつリアルタイムで最終的なコンテンツの見た目をプレビューできるようになりました。コンテンツに変更を加えるとすぐにプレビューにも反映されます。
イメージとしては↓の感じです。
ライブプレビュー機能について
もう少し具体的な説明に移ります。
ライブプレビューは大きく3つの機能で構成されます。
1. 並列プレビュー&編集(Side-by-side previewing and editing)
一画面で Contentful エディタとフロントアプリを並行して表示する機能です。
指定した URL のフロントアプリケーションを Contentful の iframe 内に呼び出すことで実現しています。
こちらの機能は開発不要ですぐに利用し始めることができる分、↑に書いた以上の機能はなく、あくまでフロントの更新orビルドを待つ必要がある点に留意が必要です。
個人的に今回のアップデートの肝は次に紹介する 2&3 です。
2. ライブアップデート(Live updates)
Contentfulのエントリー(画面左側)を編集すると、フロントアプリケーションを更新しなくても、変更内容がプレビューペイン(画面右側)に同時に表示されます。
プレビュー反映の待ち時間が必要なく、かつ最終的な見た目を確認しつつ記事編集ができるため、開発者・記事執筆者ともに待ち望まれていた機能かと思います。
エディタの種別に関係なく変更がほぼ一瞬で反映されるため、記事執筆のストレスが大幅に軽減されます。
後述するように SDK は JS で書かれており、コード側に記述されたフラグを利用して該当箇所の DOM を書き換えているのかな、と推測してます。
なお、プレビューに反映された変更は [Publish] しないと本番サイトには反映されない点は従来通り。
3. インスペクターモード(Inspector mode)
プレビューペイン(画面右側)の任意の部分にカーソルを合わせ、[編集] をクリックすると、そのソース フィールドにすばやくジャンプし、すぐに編集が開始できます。
記事執筆者から開発者へのよくある質問「記事の○○の部分ってCMSのどこから編集すればいいの?」を解決してくれる機能です。執筆体験がより直感的になりますね。
ライブプレビューの実装・開発
1. 各機能の導入方法
機能 | 導入方法 |
---|---|
1. 並列プレビュー&編集 | 開発不要 / コンソールからプレビューアプリURLを指定 |
2. ライブアップデート | SDKによる開発/アプリケーション変更が必要 |
3. インスペクターモード | SDKによる開発/アプリケーション変更が必要 |
2. 開発の前提
- Node.js:
>=16.15.1
- SDK: https://github.com/contentful/live-preview
ライブプレビュー SDK は JavaScript で動作し、React に最適化されています。上記 Github には Next.js, Gatsby, Remix のサンプルも用意されています。
次に、開発が必要な 2 つの機能について詳細を説明します。
3. ライブプレビュー SDK の初期化
まず、Contentful とプレビューアプリの通信を確立するため、コンポーネントをラップするプロバイダ( ContentfulLivePreviewProvider
)で SDK を初期化します。
import { ContentfulLivePreviewProvider } from '@contentful/live-preview/react'; const App = ({ Component, pageProps }) => ( <ContentfulLivePreviewProvider locale="en-US"> <Component {...pageProps}> </ContentfulLivePreviewProvider> )
プロバイダはオプションが利用できます。
<ContentfulLivePreviewProvider locale="set-your-locale-here" // 必須 enableInspectorMode={false} // オプション。デフォルトは true 。 enableLiveUpdates={false} // オプション。デフォルトは true 。 debugMode={false} // オプション。デフォルトは false 。 >
enableInspectorMode
や enableLiveUpdates
を OFF にするとライブプレビュー機能が無効化され、data-attributes のようなライブプレビューに利用される特定のデータ生成も行われなくなるようです。そのため本番環境などでは(環境変数などで)無効化しておく設定が推奨かと思われます。
4. ライブアップデート機能の実装
React でライブアップデートを設定するには useContentfulLiveUpdates()
フックをインポートして、元のソースデータ(記事データなど)を渡します。フックは Contentful コンソール上でライブ更新されたデータを返します。
import { useContentfulLiveUpdates } from '@contentful/live-preview/react'; const updatedEntries = useContentfulLiveUpdates(entries);
また GraphQL においてライブアップデートのパフォーマンスを安定・向上させるためには、クエリを直接 useContentfulLiveUpdates()
に渡すことが推奨されています。
const query = gql` query posts { postCollection(where: { slug: "${slug}" }, preview: true, limit: 1) { items { __typename sys { id } title content: description } } } ` // ... const updated = useContentfulLiveUpdates(originalData, { query }) // ...
5. インスペクターモードの実装
インスペクターモードを有効にするには、コード側の該当するフィールドにタギングを行います。タギングは該当の HTML タグにデータ属性を記述することで有効化され、ライブプレビュー SDK が要素をスキャンできるようになります。
下記の例では Text
コンポーネントにタギング(属性付与)を行なっています。
export default function BlogPost: ({ blogPost }) { const inspectorProps = useContentfulInspectorMode() // Live updates for this component const data = useContentfulLiveUpdates( blogPost ); return ( <Section> <Heading as="h1">{data.heading}</Heading> <Text as="p" {...inspectorProps({ entryId: data.sys.id, fieldId: 'text', })}> {data.text} </Text> </Section> ); }
また複数のフィールドのタギングを行う場合には、useContentfulInspectorMode
フックに初期値を入れておくこともできます。
export default function BlogPost: ({ blogPost }) { const inspectorProps = useContentfulInspectorMode({ entryId: data.sys.id }) return ( <Section> <Heading as="h1" {...inspectorProps({ fieldId: 'heading' })}>{data.heading}</Heading> <Text as="p" {...inspectorProps({ fieldId: 'text' })}> {data.text} </Text> </Section> )
6. Contentful 側での有効化
Contentful 側の準備で必要なのは [Settings] → [Content preview] に進み、該当のモデルに対してプレビューアプリの URL を入力するのみです。 URL には変数が利用できます。詳しくはこちらから。
7. 実装例
私の Gatsby の個人ブログをベースに実装例を紹介します。なお Contentful との接続は gatsby-source-contentful
で行なっています。
まずは初期化。
import React from 'react'; import { ContentfulLivePreviewProvider } from '@contentful/live-preview/react'; import '@contentful/live-preview/style.css'; export const wrapRootElement = ({ element }) => ( <ContentfulLivePreviewProvider locale="en-US"> {element} </ContentfulLivePreviewProvider> );
次に記事ページ。
例えば Contentful でフィールド定義された title
(短文テキストフィールド)と content
(マークダウン長文フィールド)をレンダリングするための書き方として、下記の例があります。
Before
import React from 'react' import { Link, graphql } from "gatsby"; const BlogPost = ({ data }) => { const post = data.contentfulBlogPost return ( <div> <h1> {post.title || ''} </h1> {post.markdownContent && <div /> } </div> ); }; export default BlogPost; export const pageQuery = graphql` query( $slug: String) { contentfulBlogPost(slug: { eq: $slug }) { title markdownContent{ childMarkdownRemark { html } } } } `;
上記をライブプレビューに対応させるため、下記の記述に変更します(追記箇所はハイライトしました)。
After
import React from 'react'; import { graphql } from "gatsby"; import { useContentfulInspectorMode, useContentfulLiveUpdates, } from '@contentful/live-preview/react'; const BlogPost = ({ data }) => { const post = data.contentfulBlogPost; const inspectorProps = useContentfulInspectorMode({ entryId: post.contentful_id }); const updatedPost = useContentfulLiveUpdates({ ...post, sys: { id: post.contentful_id }, }); return ( <> <div> <h1 {...inspectorProps({ fieldId: 'title' })} > {updatedPost.title || ''} </h1> {updatedPost.markdownContent && <div {...inspectorProps({ fieldId: 'markdownContent' })} dangerouslySetInnerHTML={{ __html: updatedPost.markdownContent.childMarkdownRemark.html }} /> } </div> </> ); }; export default BlogPost; export const pageQuery = graphql` query( $slug: String) { contentfulBlogPost(slug: { eq: $slug }) { __typename contentful_id title markdownContent{ childMarkdownRemark { html } } } } `;
useContentfulLiveUpdates()
は渡された記事のオリジナルデータとユニーク ID を元に、Contentful 側で更新された記事データをリアルタイムで返します。inspectorProps
は、Contentful 側のインスペクターモードから該当のレンダリング箇所とフィールドを紐づけるための属性を付与しています。ここは画面上でのポインティングに利用されるため、ラップされていても問題ありません。- GraphQL クエリ に追加した
__typename
とcontentful_id
は、Contentful がプレビュー記事との紐付けに利用するユニーク ID の役割を果たします。
注意点
認証クッキーなどの属性
iframe 内で実行されるライブプレビューにクッキーを渡すには SameSite=None; Secure
フラグを設定します。例えば認証クッキーを使ってプレビューサイトにログインする場合、次のような記述になります:
Set-Cookie: auth=abc123; SameSite=None; Secure
セキュリティポリシーへの対応
ライブプレビュー画面で「接続を拒否されました」のメッセージが表示される場合、プレビューアプリ側のセキュリティ設定(セキュリティヘッダーまたはコンテンツセキュリティポリシー = CSP)が原因の可能性があります。
その場合は下記の方法での回避をご検討ください。
X-Frame-Options header
を取り除く- Content-Security-Policy ヘッダーに
frame-ancestors https://app.contentful.com
を追加する
最後に
以上、 Contentful(たぶん待望の)新機能であるライブプレビューを紹介してみました。
やはり肝心な点は、これが既存エディターの追加機能ではなく、Contentful のモデルから独立した新機能/SDKである点かと思います。そのため、記事モデルがどのようなフィールドもしくはエディター(カスタムアプリ含む)で構成されている場合でも、この機能は実装できるのが嬉しいところかと思います。
ヘッドレス CMS というコンセプトはあくまで開発者ファーストな表現だと思うので、今回のように純粋にスピーディで心地よい執筆体験を提供するアップデートがあると、編集者・執筆者サイドの方々にもより Contentful を勧めやすくなるなぁ、と感じました。
クラスメソッドでは Contentful の契約のご相談、構築支援をしています。ご興味のある方はぜひ弊社までお問い合わせください。
参考資料
- https://www.contentful.com/developers/docs/tutorials/general/live-preview/
- Live preview に関する概要説明
- https://github.com/contentful/live-preview
- Live Preview の SDK のリポジトリ